/**
 * Copyright (C) 2011  Robin Dietrich, Nils Kenneweg, Leonard Stutz
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 **/
#ifndef CHUNK_HPP
#define	CHUNK_HPP

#include <string>
#include <boost/multi_array.hpp>
#include <boost/array.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/unordered_set.hpp>
#include <boost/thread.hpp>

#include "blockManager.hpp"

struct coordinate {
	int x;
	int y;
	int z;
};

struct localCoordinates {
	coordinate local;
	coordinate chunk;
};

namespace minecraft {
    inline std::vector<std::string> split(std::string string, const char delimiter, bool keepempty) {
        std::stringstream s(string);
        std::string t;
        std::vector<std::string> r;
        while (std::getline(s, t, delimiter)) {
            if (keepempty || t.size() != 0) {
                r.push_back(t);
            }
        }
     
        return r;
    }

    inline std::string coordinatesToChunkId(int x, int y, int z) {
        return boost::algorithm::replace_all_copy(boost::lexical_cast<std::string>(x)+"."+boost::lexical_cast<std::string>(y)+"."+boost::lexical_cast<std::string>(z), "-", "F");
    }
	
    inline coordinate chunkIdToCoordinates(std::string ChunkID) {
		coordinate c;
	
        std::vector<std::string> parts = split(ChunkID, '.', false);
		if (parts.size() == 3) {
			for(int i = 0; i < 3; ++i)
				std::replace(parts[i].begin(), parts[i].end(), 'F', '-');
			
			c.x = boost::lexical_cast<int>(parts[0]);
			c.y = boost::lexical_cast<int>(parts[1]);
			c.z = boost::lexical_cast<int>(parts[2]);
		} else {
			c.x = 0;
			c.y = 0;
			c.z = 0;
			
			std::cout << "Not a valid chunkID: " << ChunkID << "\n";
		}
		
		return c;
    }
	
	//inline void 
    
	struct chunk_id {
		/** x position of the chunk */
		int x;
		/** y position of the chunk */
		int y;
		/** z position of the chunk */
		int z;
		/** unique identifer constructed from x/y/z */
		std::string id;
	};

	/** typedef for 3D array with boost::multi_array */
	typedef boost::multi_array<b_type, 3> b_array;
	/** typedef for array index (quick and easy output) */
	typedef b_array::index b_array_i;
    /** typedef for a list of different blocks used in the chunk */
    typedef boost::unordered_set<b_type> b_tlist;

	/** chunk class containing one chunk of the map */
	class chunk {
		private:
			/** id of the chunk containing the position */
			chunk_id id;
			/** data, array via x/y/z */
			b_array data;
            /** block types used in the chunk */
            b_tlist types;
            /** whether the chunk was modified or not */
            bool changed;
			/** Mutex Lock for Data **/
			boost::mutex data_lock;
		public:
            /** constructor, takes chunk position to load data */
			chunk(int x, int y, int z);
			/** destructor, frees data and saves changes to disk */
			~chunk();
			
            /** returns block id as string */
            std::string getId() {
                return coordinatesToChunkId(this->id.x, this->id.y, this->id.z);
            }
			
			int getX() {
				return this->id.x;
			}
			
			int getY() {
				return this->id.y;
			}
			
			int getZ() {
				return this->id.z;
			}
			
            /** returns whether whole chunk is empty */
            bool isEmpty() {
                return (this->types.size() == 0);
            }
            /** returns types used in this chunk */
            b_tlist& getTypes() {
                return this->types;
            }            
            /** rebuilds the type index */
            void rebuildTypes();
            
            
            /** returns value of block */
            b_type get(int x, int y, int z);
            /** updates a single block with given value */
            void update(int x, int y, int z, b_type val);            
	};
}

/** non-intrusive implementation for serialization of boost::multi_array / minecraft::b_array */
namespace boost {
    namespace serialization {
        /** load function*/
        template<class Archive> void load(Archive & ar, minecraft::b_array & t, const unsigned int file_version) {
            // typedef for easy handling
            typedef typename minecraft::b_array::size_type size_;

            //get dimensions
            size_ n0;
            ar >> BOOST_SERIALIZATION_NVP(n0);
            size_ n1;
            ar >> BOOST_SERIALIZATION_NVP(n1);
            size_ n2;
            ar >> BOOST_SERIALIZATION_NVP(n2);

            //set array dimensions and input data
            t.resize(boost::extents[n0][n1][n2]);
            ar >> make_array(t.data(), t.num_elements());
        }

        /** save function */
        template<class Archive> void save(Archive & ar, const minecraft::b_array & t, const unsigned int file_version) {
            // typedef for easy handling
            typedef typename minecraft::b_array::size_type size_;

            //save dimensions
            size_ n0 = (t.shape()[0]);
            ar << BOOST_SERIALIZATION_NVP(n0);
            size_ n1 = (t.shape()[1]);
            ar << BOOST_SERIALIZATION_NVP(n1);
            size_ n2 = (t.shape()[2]);
            ar << BOOST_SERIALIZATION_NVP(n2);

            //save data
            ar << make_array(t.data(), t.num_elements());
        }

        /** serialize function, called with overloaded << parameter */
        template<class Archive> void serialize(Archive & ar, minecraft::b_array & t, const unsigned int file_version) {
            split_free(ar, t, file_version);
        }
    }
}

#endif